package org.dreamwork.jasmine2.compiler;

import org.apache.log4j.Logger;
import org.dreamwork.fs.FolderWalker;
import org.dreamwork.fs.IFileHandler;
import org.dreamwork.jasmine2.javac.JasmineClassLoader;
import org.dreamwork.jasmine2.javac.JasmineCreator2;
import org.dreamwork.jasmine2.javac.JasmineFileInfo;
import org.dreamwork.jasmine2.javac.configuration.ConfigurationParser;
import org.dreamwork.jasmine2.parser.CharsetDetector;
import org.dreamwork.jasmine2.parser.JspDirective;
import org.dreamwork.jasmine2.parser.ParseException;
import org.dreamwork.jasmine2.parser.TaglibDirective;
import org.xml.sax.SAXException;

import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by IntelliJ IDEA.
 * User: seth.yang
 * Date: 2010-4-12
 * Time: 13:24:15
 */
public class JasmineCompiler implements IFileHandler {
    private String distDir;
    private String srcDir;
    private String baseDir;
    private String classPath;
    private String currentFileName;
    private File dist, src;
    private int cursor;
    private boolean walkDone;
    private Runnable runner;
    private final Pattern pattern = Pattern.compile ("<%@\\s*taglib\\s+.*%>");
    private static final Logger logger = Logger.getLogger (JasmineCompiler.class);

    private List<File> files = Collections.synchronizedList (new ArrayList<File> ());
    private final CompilerWorker worker = new CompilerWorker ();

    public String getDistDir () {
        return distDir;
    }

    public void setDistDir (String distDir) {
        this.distDir = distDir;
    }

    public String getSrcDir () {
        return srcDir;
    }

    public void setSrcDir (String srcDir) {
        this.srcDir = srcDir;
    }

    public String getBaseDir () {
        return baseDir;
    }

    public void setBaseDir (String baseDir) {
        this.baseDir = baseDir;
    }

    public int getCursor () {
        return cursor;
    }

    public boolean isWalkDone () {
        return walkDone;
    }

    public void setRunner (Runnable runner) {
        this.runner = runner;
    }

    public String getCurrentFileName () {
        return currentFileName;
    }

    public void compile () throws IOException, SAXException {
        File file = new File (baseDir);
        if (!file.exists ())
            throw new IllegalArgumentException ("directory [" + srcDir + "] does not exits");

        file = new File (distDir);
        if (!file.exists ()) {
            if (!file.mkdirs ())
                throw new IllegalArgumentException ("Can't create distination directory [" + distDir + "]");
        }
        dist = file;

        file = new File (srcDir);
        if (!file.exists ()) {
            if (!file.mkdirs ())
                throw new IllegalArgumentException ("Can't create distination directory [" + srcDir + "]");
        }
        src = file;

        parseConfig ();

        FolderWalker walker = new FolderWalker (baseDir);
        walker.setFileFilter ("*.jasmine|*.jscx");
        walker.addFileHandler (this);
        walker.setCreateIndex (false);

        worker.start ();

        walker.walk ();
        walkDone = true;
        System.out.println ("walk done");
    }

    public void processFile (File file) {
        files.add (file);
        synchronized (worker) {
            if (!worker.running) worker.notifyAll ();
        }
    }

    private void compilePage (File file) {
        System.out.printf ("compile file %s%n", file.getAbsolutePath ());
        currentFileName = file.getAbsolutePath ();
        String path = file.getAbsolutePath ();
        String name = path.substring (baseDir.length ());
        name = name.replace (File.separatorChar, '/');
        JasmineFileInfo info = new JasmineFileInfo (name, src, dist);
        List<TaglibDirective> list;

        try {
            String charset = new CharsetDetector (file.toURL ()).getCharset ();
            Reader in = new InputStreamReader (new FileInputStream (file), charset);
            list = scan (in);
            in.close ();
            String currentPath = file.getParentFile ().getAbsolutePath ();
            currentPath = currentPath.substring (baseDir.length ());
            Map<String, String> map = new HashMap<String, String> ();
            if (list != null) for (TaglibDirective taglib : list) {
                String userControlFile = currentPath + '/' + taglib.getURI ();
                logger.debug ("find user control: " + userControlFile);
                JasmineFileInfo fileInfo = new JasmineFileInfo (userControlFile, src, dist);
                fileInfo.createPackageDir ();
                if (needCreatePage (fileInfo)) {
                    logger.debug ("preparing to create user control " + userControlFile);
                    createPage (fileInfo, null);
                    logger.debug (userControlFile + " created as " + fileInfo.getClassFile ());
                }
                map.put (taglib.getName (), fileInfo.getClassName ());
            }

            if (needCreatePage (info)) createPage (info, map.size () == 0 ? null : map);
            new JasmineClassLoader (info.getClassDir ());
            if (runner != null) new Thread (runner).start ();
        } catch (Exception ex) {
            throw new RuntimeException (ex);
        }
    }

    private List<TaglibDirective> scan (Reader reader) throws IOException {
        char[] buff = new char[1024];
        int length;
        StringBuffer sb = new StringBuffer ();
        while ((length = reader.read (buff)) != -1) {
            sb.append (buff, 0, length);
        }

        Matcher m = pattern.matcher (sb);
        List<TaglibDirective> list = new ArrayList<TaglibDirective> ();
        while (m.find ()) {
            String group = m.group (0);
            TaglibDirective taglib = (TaglibDirective) JspDirective.getDirective (group);
            taglib.parse ();
            if (taglib.getName () != null)
                list.add (taglib);
        }

        return list;
    }

    private boolean needCreatePage (JasmineFileInfo info) {
        if (!info.getClassFile ().exists ()) return true;
        File jsp = new File (baseDir + "/" + info.getOrignalPath ());
        return jsp.lastModified () > info.getClassFile ().lastModified ();
    }

    private void createPage (JasmineFileInfo info, Map<String, String> userControls) throws IOException, ParseException {
        File jsp = new File (baseDir + "/" + info.getOrignalPath ());
        String charset = new CharsetDetector (jsp.toURL ()).getCharset ();
        Reader in = new InputStreamReader (new FileInputStream (jsp), charset);
        JasmineCreator2 creator = new JasmineCreator2 ();
        creator.setCompileClassPath (getClassPath ());
        if (userControls != null) {
            creator.setUserControl (userControls);
            creator.setUserControlClassPath (info.getClassDir ());
        }
        creator.setReader (in);
        creator.create (info);
        in.close ();
    }

    public int getTotalFiles () {
        return files.size ();
    }
    
    private String getClassPath () {
        if (classPath != null) return classPath;
        StringBuilder builder = new StringBuilder ();
        ClassLoader loader = getClass ().getClassLoader ();
        Set<URL> set = new HashSet<URL> ();
        while (loader != null) {
            if (!(loader instanceof URLClassLoader)) continue;
            URLClassLoader ucl = (URLClassLoader) loader;
            set.addAll (Arrays.asList (ucl.getURLs ()));
            loader = loader.getParent ();
        }
        for (URL url : set) {
            if (builder.length () > 0) builder.append (File.pathSeparator);
            builder.append (url.getFile ());
        }

        builder.append (File.pathSeparator).append (dist.getAbsolutePath ());
        File file = new File (baseDir + "/WEB-INF");
        if (file.exists ()) {
            String classes = baseDir + "/WEB-INF/classes";
            File classDir = new File (classes);
            if (classDir.exists ()) builder.append (File.pathSeparator).append (classDir.getAbsolutePath ());

            String libs = baseDir + "/WEB-INF/lib";
            File libDir = new File (libs);
            if (libDir.exists ()) {
                File[] jars = libDir.listFiles (new FileFilter() {
                    public boolean accept (File pathname) {
                        String name = pathname.getName ().toLowerCase ();
                        return name.endsWith (".jar") || name.endsWith (".zip");
                    }
                });
                for (File jar : jars) builder.append (File.pathSeparator).append (jar.getAbsolutePath ());
            }
        }

        classPath = builder.toString ();
        return classPath;
    }

    public void processDir (File dir) {

    }

    private void parseConfig () throws IOException, SAXException {
        String s = getClassPath ();
        String[] a = s.split (File.pathSeparator);
        URL[] urls = new URL[a.length];
        for (int i = 0; i < a.length; i ++) {
            File file = new File (a [i]);
            if (file.isDirectory ())
                urls [i] = new URL (null, new URL ("file", null, file.getCanonicalFile () + File.separator).toString (), (URLStreamHandler) null);
            else
                urls [i] = new URL (null, new URL ("file", null, file.getCanonicalFile ().toString ()).toString (), (URLStreamHandler) null);
        }
        ClassLoader loader = new URLClassLoader (urls);
        ConfigurationParser.parse (loader);
    }

    private class CompilerWorker extends Thread {
        boolean running = false;
        @Override
        public void run () {
            while (files.size () == 0) {
                synchronized (this) {
                    try {
                        wait ();
                    } catch (InterruptedException e) {
                        e.printStackTrace ();
                    }
                }
            }

            running = true;
            while (cursor < files.size ()) {
                File file = files.get (cursor ++);
                compilePage (file);
            }
        }
    }

    public static void main (String[] args) throws Exception {
        JasmineCompiler compiler = new JasmineCompiler ();
        compiler.baseDir = "D:\\workspace\\java\\gae-test\\Twin\\web";
        compiler.srcDir  = "d:/tmp/jasmine-compiler/src";
        compiler.distDir = "d:/tmp/jasmine-compiler/classes";
        compiler.compile ();
    }
}